 
/* 
 * Copyright (C) Ignacio Lucas
 */
 
/*
 *  timer_service, hecho Ignacio Lucas
 *
 *  Encapsulamiento de mecanismo para administrar timers sobre objetos.
 *  Esta implementacion hace uso de un arbol rbtree, siguiendo el ejemplo utilizado por nginx.
 *
 *  Ignacio Lucas <ignacio.lucas@gmail.com>
 */
#include "common.h"
#include "timer_service.h"
#include "utils/rbtree.h"
#include "utils/timer_utils.h"
#include "common_objects_pool.h"


typedef struct s_timer_service
{
	unsigned int max_events;

	// estructura de arbol para administrar los timers ordenados junto con el nodo sentinela.
	rbtree_t  timer_tree;
	rbtree_node_t event_timer_sentinel;
	
	// fd donde escribir un byte cada vez que se detecta un nuevo proximo "menor timeout".
	int fd_notif;

	// usuario que invoca a este servicio.
	void *caller;

	// funcion de callback para el procesamiento de timeouts.
	timeout_event_callback callback_func;
 
} timer_service;

// por ahora, alcanza con un singleton que administra los timers.
static timer_service main_timer_service;

timer_service_t timer_service_init(int fd_notif, void *caller,timeout_event_callback callback_func)
{
    timer_service_t ts = NULL;

	ts=&(main_timer_service);
    ts->fd_notif = fd_notif;
	ts->caller = caller;
	ts->callback_func = callback_func;

	rbtree_sentinel_init(&(ts->event_timer_sentinel));
    rbtree_init(&(ts->timer_tree), &(ts->event_timer_sentinel), rbtree_insert_timer_value);

  	return (timer_service_t)ts;
}

size_t timer_service_get_timer_size()
{
	return rbtree_get_node_size();
}

inline int timer_service_remove_timeout(timer_service_t ts, job_event_t event)
{
	rbtree_delete(&(ts->timer_tree), (rbtree_node_t *)event->timer_ref);
	return 0;
}

inline int timer_service_register_timeout(timer_service_t ts, job_event_t event)
{
	timer_absolute_t new_time;
	char notify_timeout_update = 0;
	rbtree_node_t *min;

	new_time = timer_utils_get_current_time() + (event->timeout_config.delta_timeout);

	logger_core_info( "[timer_service] new_time: %u", new_time);

	// Esto quiere decir que hay al menos un elemento.
    if (ts->timer_tree.root != &(ts->event_timer_sentinel)) {

		min = rbtree_min(ts->timer_tree.root, &(ts->event_timer_sentinel));
		if (new_time < (min->key))
		{
			notify_timeout_update = 1;
		}
	}
	else
	{
		// primer elemento al arbol si o si hay que avisar...
		notify_timeout_update = 1;		
	}

	// agregamos un nuevo nodo al arbol de timeouts.
	rbtree_node_t *new_node = (rbtree_node_t*)common_objects_pool_get(TIMER_SERVICE_POOL);
	if (new_node == NULL)
	{
		// error, sin memoria disponible.
		return 1;
	}

	new_node->user_data = event;
	new_node->key = (rbtree_key_t)new_time;
	rbtree_insert(&(ts->timer_tree),new_node);

	// asociamos al nuevo nodo dentro del evento.
	event->timer_ref = (void *)new_node;	

	if (notify_timeout_update == 1)
	{
		if (write(ts->fd_notif, "1",1) != 1)
		{
	  		logger_io_warn("[timer_service] error notificando actualizacion de timers. error=%d", errno);
		}
		else
		{
			logger_core_info("%s","[timer_service] informo actualizacion de timer");
		}
	}
	return 0;
}


static inline job_event_t timer_service_get_next_event_timeout(timer_service_t ts, rbtree_key_t *timeout, rbtree_key_t default_timeout)
{
	job_event_t event = NULL;
	rbtree_node_t *min;
	timer_absolute_t currtime;

	// por default asumo que el valor proximo de timeout a devolver sera el default
	*timeout = default_timeout;

	// hay algun elemento en el arbol?
    if (ts->timer_tree.root != &(ts->event_timer_sentinel))
	{
		min = rbtree_min(ts->timer_tree.root, &(ts->event_timer_sentinel));

		// si el evento ya expiro su timeout, lo borro del arbol y lo preparo para devolver.
		currtime=timer_utils_get_current_time();
		logger_core_error( "[timer_service] currtime: %u!", currtime);
		if ((min->key) <= (currtime))
		{
			logger_core_error( "[timer_service] expira timer. key: %u, currtime: %u!", (timer_absolute_t)min->key, currtime);
			event = (job_event_t)min->user_data;

			rbtree_delete(&(ts->timer_tree), min);

			// devuelvo el nodo al pool.
			common_objects_pool_put(TIMER_SERVICE_POOL, min);
		}
		else
		{
			*timeout=(min->key)-(currtime);
			logger_core_error( "[timer_service] propone nuevo timeout %d", (timer_absolute_t)*timeout);

		}
	}
	return event;

}

inline void timer_service_process_timeouts(timer_service_t t, int *next_event_delta_time, int default_delta_time_no_pending_timers)
{
	timer_service *ts=(timer_service *)t;
	job_event_t event = NULL;
	rbtree_key_t timeout;

	while ((event = timer_service_get_next_event_timeout(ts,&timeout, default_delta_time_no_pending_timers)) != NULL)
	{
		logger_core_error("%s","[timer_service] llamo callback por timeout");
		ts->callback_func(event, ts->caller);
	}
	*next_event_delta_time=(int)timeout;
	return;
}

int timer_service_destroy(timer_service_t t)
{
	// los nodos del rbtree se destruiran al liberar el pool de nodos.
	return 0;
}

